/*
 * Copyright (c) 2010-2013 Ruben Chakhmakhchyan. All rights reserved.
 * 
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Eclipse Public License
 * Version 1.0 (the 'License'). You may not use this file except in
 * compliance with the License. Please obtain a copy of the License at
 * http://opensource.org/licenses/EPL-1.0 and read it before using this file.
 */

#include "jpeg_encoder.h"

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include <windows.h> // include windows.h before jpeglib.h to avoid type redefition

#pragma warning( disable : 4996 )

#define JPEG_INTERNALS
#include "jpeglib.h"

#include "dib.h"


// Compresses pixel buffer to JPEG
int JpegEncoder::encode( byte_t** dest, const Pixels& p, int quality )
{
    long width = p.width;
    long height = p.height;
    bool bitsInverted = p.paletteInverted;
    uint_t bitCount = p.bitsPerPixel;
    uint_t stride = p.rowStride;
    long dpi = p.resolution;

    ulong_t outlen = 0;
    struct jpeg_compress_struct cinfo = { 0 };
    struct jpeg_error_mgr jerr;
    //JSAMPROW rowPointer[ 1 ];

    *dest = 0;

    cinfo.err = ::jpeg_std_error( &jerr );

    ::jpeg_create_compress( &cinfo );
    ::jpeg_mem_dest( &cinfo, dest, &outlen );

    switch ( bitCount )
    {
    case 1:
    case 8:
        cinfo.input_components = 1;
        cinfo.in_color_space = JCS_GRAYSCALE;
        break;

    case 24:
        cinfo.input_components = 3;
        cinfo.in_color_space = JCS_RGB;
        break;
    }

    ::jpeg_set_defaults( &cinfo );

    cinfo.image_width = width;
    cinfo.image_height = height;
    cinfo.density_unit = 1; // 0 - unknown, 1 - dots/inch, 2 - dots/cm
    cinfo.Y_density = ( UINT16 ) dpi;
    cinfo.X_density = ( UINT16 ) dpi;

    ::jpeg_set_quality ( &cinfo, quality, false );

    ::jpeg_start_compress( &cinfo, TRUE );

    byte_t* line = p.pixels;

    byte_t* byteLine = 0; // byte-represented bit line

    if ( bitCount == 1 )
    {
        byteLine = ( byte_t* ) ::malloc( width ); //new byte_t[ width ];
    }

    byte_t* scanline = 0;

    while ( cinfo.next_scanline < cinfo.image_height )
    {
        scanline = line;

        if ( bitCount == 1 )
        {
            for ( int i = 0; i < width; i++ )
            {
                int byteIndex = i >> 3;
                byte_t* base = line + byteIndex;
                byte_t baseChar = *base;
                byte_t bitIndex = i & 7;
                byte_t bit = ( baseChar >> ( 7 - bitIndex ) ) & 0x01;
                byte_t bitValue = bitsInverted ? bit : !bit;

                byteLine[ i ] = bitValue ? 0xFF : 0x00;//bitValue;
            }

            scanline = byteLine;
        }

        ::jpeg_write_scanlines( &cinfo, &scanline, 1 );

        line += stride;
    }

    ::jpeg_finish_compress( &cinfo );
    ::jpeg_destroy_compress( &cinfo );

    if ( byteLine ) delete byteLine;

    return outlen;
}

// Decompresses JPEG to DIB
size_t JpegEncoder::decode( byte_t** dest, byte_t* source, size_t sourceLength, size_t width, size_t height, byte_t bitCount, char direction, bool bitsInverted, uint_t dpi )
{
    struct jpeg_decompress_struct cinfo;
    struct jpeg_error_mgr jerr;

    cinfo.err = ::jpeg_std_error( &jerr );
    ::jpeg_create_decompress( &cinfo );

    ::jpeg_mem_src( &cinfo, source, sourceLength );

    switch ( bitCount )
    {
    case 1:
    case 8:
        cinfo.output_components = 1;
        cinfo.out_color_space = JCS_GRAYSCALE;
        break;

    case 24:
        cinfo.output_components = 3;
        cinfo.out_color_space = JCS_RGB;
        break;
    }

    ::jpeg_read_header( &cinfo, true );
    ::jpeg_calc_output_dimensions( &cinfo );
    ::jpeg_start_decompress( &cinfo );

    size_t dibSize = Dib::GetSize( width, height, bitCount );
    *dest = ( byte_t* ) ::malloc( dibSize );
    size_t dibStride = Dib::GetStride( width, bitCount );

    int bytesPerRow = cinfo.output_width * 3;
    //scanline = ( *cinfo.mem->alloc_sarray ) ( ( j_common_ptr ) &cinfo, JPOOL_IMAGE, bytesPerRow, 1 ); // initialize the array of scanlines
    byte_t* scanline = ( byte_t* ) ::malloc( bytesPerRow ); // output row buffer JSAMPARRAY
    ::memset( scanline, 0, dibStride );

    byte_t* destLine = *dest + dibSize;

    while ( cinfo.output_scanline < cinfo.output_height )
    { 
        ::jpeg_read_scanlines( &cinfo, &scanline, 1 );

        //if ( jpegError )
        //{
        //	break;
        //}

        destLine -= dibStride;

        ::memcpy( destLine, scanline, dibStride );
    }

    ::jpeg_finish_decompress( &cinfo );
    ::jpeg_destroy_decompress( &cinfo );

    delete scanline;

    return dibSize;
}
